#include "Lowering/Lowerer.hpp"
#include "Binding/BoundAssignment.hpp"
#include "Binding/BoundBinary.hpp"
#include "Binding/BoundBinaryOperator.hpp"
#include "Binding/BoundBlock.hpp"
#include "Binding/BoundConditionalGotoStatement.hpp"
#include "Binding/BoundExpressionStatement.hpp"
#include "Binding/BoundForStatement.hpp"
#include "Binding/BoundGotoStatement.hpp"
#include "Binding/BoundIfStatement.hpp"
#include "Binding/BoundLabelStatement.hpp"
#include "Binding/BoundLiteral.hpp"
#include "Binding/BoundVariable.hpp"
#include "Binding/BoundVariableDeclaration.hpp"
#include "Binding/BoundWhileStatement.hpp"

#include "Symbol/TypeSymbol.hpp"
#include "Syntax/Object/Integer.hpp"

#include <cstddef>
#include <format>
#include <memory>
#include <stack>
#include <vector>

SharedBoundLabel Lowerer::GenerateLabel()
{
    auto name = std::format("Lable{}", ++this->LableCount);
    return std::make_shared<BoundLabel>(name);
}

SharedBoundBlockStatement Lowerer::Lower(SharedBoundStatement statement)
{
    auto lowerer = std::make_shared<Lowerer>();
    auto result  = lowerer->RewriteStatement(statement);
    return Lowerer::Flatten(result);
}

SharedBoundBlockStatement Lowerer::Flatten(SharedBoundStatement statement)
{
    std::vector<SharedBoundStatement> builder;

    std::stack<SharedBoundStatement> stack;

    stack.push(statement);
    while (stack.size() > 0)
    {
        auto current = stack.top();
        stack.pop();
        if (current->GetKind() == BoundNodeKind::BlockStatement)
        {
            auto boundBlockStatement = ((BoundBlockStatement *)current.get())->Statements;
            auto statementMax        = boundBlockStatement.size();
            for (size_t s = 0; s < statementMax; s++)
            {
                stack.push(boundBlockStatement[statementMax - s - 1]);
            }
        }
        else
        {
            builder.push_back(current);
        }
    }
    return std::make_shared<BoundBlockStatement>(builder);
}
SharedBoundStatement Lowerer::RewriteForStatement(SharedBoundStatement node)
{
    // for <var> = <lower> to <upper>
    //      <body>
    //
    // ---->
    //
    // {
    //      var <var> = <lower>
    //      let upperBound = <upper>
    //      while (<var> <= upperBound)
    //      {
    //          <body>
    //          <var> = <var> + 1
    //      }
    // }
    BoundForStatement *forNode = (BoundForStatement *)node.get();

    auto variableDeclaration   = std::make_shared<BoundVariableDeclaration>(forNode->Variable, forNode->LowerBound);
    auto variableExpression    = std::make_shared<BoundVariableExpression>(forNode->Variable);

    auto upperBoundSymbol      = std::make_shared<VariableSymbol>("upperBound", true, TypeSymbol::GetInt());
    auto upperBoundDeclartion  = std::make_shared<BoundVariableDeclaration>(upperBoundSymbol, forNode->UpperBound);
    auto upperBoundExpression  = std::make_shared<BoundVariableExpression>(upperBoundSymbol);

    auto lessOrEqualsOp        = BoundBinaryOperator::Bind(SyntaxKind::LessOrEquals, TypeSymbol::GetInt(), TypeSymbol::GetInt());

    auto condtion              = std::make_shared<BoundBinaryExpression>(variableExpression, lessOrEqualsOp, upperBoundExpression);

    auto literal               = std::make_shared<BoundLiteralExpression>(std::make_shared<Integer>(1));
    auto plusOp                = BoundBinaryOperator::Bind(SyntaxKind::Plus, TypeSymbol::GetInt(), TypeSymbol::GetInt());
    auto binary                = std::make_shared<BoundBinaryExpression>(variableExpression, plusOp, literal);
    auto assign                = std::make_shared<BoundAssignmentExpression>(forNode->Variable, binary);

    auto increment             = std::make_shared<BoundExpressionStatement>(assign);

    std::vector<SharedBoundStatement> statements{forNode->Body, increment};

    auto whileBody      = std::make_shared<BoundBlockStatement>(statements);
    auto whileStatement = std::make_shared<BoundWhileStatement>(condtion, whileBody);

    std::vector<SharedBoundStatement> block{variableDeclaration, upperBoundDeclartion, whileStatement};

    auto result = std::make_shared<BoundBlockStatement>(block);

    return this->RewriteStatement(result);
}

SharedBoundStatement Lowerer::RewriteIfStatement(SharedBoundStatement node)
{
    auto ifNode = (BoundIfStatement *)node.get();

    if (ifNode->ElseStatement == nullptr)
    {
        // if <condition>
        //      <then>
        //
        // ---->
        //
        // gotoFalse <condition> end
        // <then>
        // end:
        auto endLabel          = this->GenerateLabel();
        auto gotoFalse         = std::make_shared<BoundConditionalGotoStatement>(endLabel, ifNode->Condition, false);
        auto endLabelStatement = std::make_shared<BoundLabelStatement>(endLabel);

        std::vector<SharedBoundStatement> statements{gotoFalse, ifNode->ThenStatement, endLabelStatement};
        auto                              result = std::make_shared<BoundBlockStatement>(statements);
        return this->RewriteStatement(result);
    }
    else
    {
        // if <condition>
        //      <then>
        // else
        //      <else>
        //
        // ---->
        //
        // gotoFalse <condition> else
        // <then>
        // goto end
        // else:
        // <else>
        // end:

        auto elseLabel          = GenerateLabel();
        auto endLabel           = GenerateLabel();

        auto gotoFalse          = std::make_shared<BoundConditionalGotoStatement>(elseLabel, ifNode->Condition, false);

        auto gotoEndStatement   = std::make_shared<BoundGotoStatement>(endLabel);
        auto elseLabelStatement = std::make_shared<BoundLabelStatement>(elseLabel);
        auto endLabelStatement  = std::make_shared<BoundLabelStatement>(endLabel);

        std::vector<SharedBoundStatement> statements{
            gotoFalse, ifNode->ThenStatement, gotoEndStatement, elseLabelStatement, ifNode->ElseStatement, endLabelStatement};
        auto result = std::make_shared<BoundBlockStatement>(statements);
        return this->RewriteStatement(result);
    }
}

SharedBoundStatement Lowerer::RewriteWhileStatement(SharedBoundStatement node)
{
    auto whileNode              = (BoundWhileStatement *)node.get();
    // while <condition>
    //      <bode>
    //
    // ----->
    //
    // goto check
    // continue:
    // <body>
    // check:
    // gotoTrue <condition> continue
    // end:
    //

    auto continueLabel          = GenerateLabel();
    auto checkLabel             = GenerateLabel();
    auto endLabel               = GenerateLabel();

    auto gotoCheck              = std::make_shared<BoundGotoStatement>(checkLabel);
    auto continueLabelStatement = std::make_shared<BoundLabelStatement>(continueLabel);
    auto checkLabelStatement    = std::make_shared<BoundLabelStatement>(checkLabel);
    auto gotoTrue               = std::make_shared<BoundConditionalGotoStatement>(continueLabel, whileNode->Condition);
    auto endLabelStatement      = std::make_shared<BoundLabelStatement>(endLabel);

    std::vector<SharedBoundStatement> statements{gotoCheck, continueLabelStatement, whileNode->BodyStatement, checkLabelStatement, gotoTrue, endLabelStatement};

    auto result = std::make_shared<BoundBlockStatement>(statements);

    return RewriteStatement(result);
}